package simplifiedPastry.simulation;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.SocketException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import simplifiedPastry.NodeId;
import simplifiedPastry.Pair;
import simplifiedPastry.exceptions.NodeStateException;

public class ClientFrame extends JFrame implements ActionListener, WindowListener, ItemListener {
	private static final long serialVersionUID = 5735777023265603469L;
	
	private JTextField address = new JTextField(40);
	private JButton connectB = new JButton("Connect");
	
	private JComboBox nodesCB = new JComboBox();
	private JButton updateB = new JButton("Update");
	private JTextArea leafSet = new JTextArea(8, 50);
	private JTextArea routingTable = new JTextArea(8, 50);
	private JTextArea output = new JTextArea(20, 50);
	
	private JButton addB = new JButton("Add");
	private JTextField addNumN = new JTextField("10");
	private JTextField addPort = new JTextField("1500");
	private JButton newNetB = new JButton("New net");
	private JButton sendRB = new JButton("Send rand");
	private JButton collectB = new JButton("Collect");
	
	private ArrayList<Server> servers = new ArrayList<Server>();
	private ArrayList<NodeId> nodes = new ArrayList<NodeId>();
	
	private BufferedReader in = null;
	private Process serverProcess = null;
	private Process rmireg = null;
	private ExecutorService	executor = Executors.newFixedThreadPool(2);
	
	public static void main(String[] args) {
		ClientFrame c = new ClientFrame();
		c.setVisible(true);
		c.setDefaultCloseOperation(ClientFrame.EXIT_ON_CLOSE);
	}
	
	public ClientFrame() {
		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		setBounds(10,10,dim.width/2, dim.height/2);
		setTitle("RMI Client for simplified-pastry");
		
		Container pane = getContentPane();
		pane.setLayout(new GridBagLayout());
		
//		BoxLayout boxLayout = new BoxLayout();
		
		JScrollPane lSP = new JScrollPane(leafSet);
		JScrollPane rtP = new JScrollPane(routingTable);
		JScrollPane oP = new JScrollPane(output);
		
		int w = 100;
		
		add(address, 0, 0, 4, 1, w, w);
		add(connectB, 4, 0, 1, 1, w, w);
		add(nodesCB, 0, 1, 3, 1, w, w);
		add(updateB, 4, 1, 1, 1, w, w);
		add(lSP, 0, 2, 5, 1, w, w);
		add(rtP, 0, 3, 5, 1, w, w);
		add(oP, 0, 4, 5, 1, w, w);
		add(addB, 0, 5, 1, 1, w, w);
		add(addNumN, 1, 5, 1, 1, w, w);
		add(addPort, 2, 5, 1, 1, w, w);
		add(newNetB, 3, 5, 1, 1, w, w);
		add(sendRB, 4, 5, 1, 1, w, w);
		add(collectB, 0, 6, 1, 1, w, w);
		
		connectB.addActionListener(this);
		updateB.addActionListener(this);
		addB.addActionListener(this);
		newNetB.addActionListener(this);
		sendRB.addActionListener(this);
		collectB.addActionListener(this);
		
		nodesCB.addItemListener(this);
		
		address.setText("127.0.0.1");
		
		this.addWindowListener(this);
		
		try {
			rmireg = Runtime.getRuntime().exec("rmiregistry");
		} catch (IOException e1) {
			e1.printStackTrace();
			System.exit(ERROR);
		}

		try {
			serverProcess = Runtime.getRuntime().exec("java -classpath /home/stach/workspace/simplified-pastry/bin -Djava.rmi.server.codebase=file:/home/stach/workspace/simplified-pastry/bin/ simplifiedPastry.simulation.ServerImpl");
			in = new BufferedReader(new InputStreamReader(serverProcess.getInputStream()));
			executor.execute(new ServerListener(in));
		} catch (IOException e1) {
			e1.printStackTrace();
			System.exit(ERROR);
		}
		
	}
	
	private void add(Component component, int x, int y, int w, int h, int wx, int wy) {
		GridBagConstraints constraints = new GridBagConstraints();
		
		constraints.gridx = x;
		constraints.gridy = y;
		constraints.gridwidth = w;
		constraints.gridheight = h;
		constraints.weightx = 100;
		constraints.weighty = 100;
		
		getContentPane().add(component, constraints);
	}
	
	private void pn(final String s) {
		EventQueue.invokeLater(new Runnable(){

			@Override
			public void run() {
				output.append(s + "\n");
				if(output.getText().length() > 2) 
					output.setCaretPosition(output.getText().length()-1);			
			}
			
		});
	}
	
	private void updateNodes(ArrayList<NodeId> n) {
		nodesCB.removeAllItems();
		nodes = n;
		if(n == null) return;
		
		for(int i = 0; i < n.size(); ++i) {
			nodesCB.addItem(i + ": " + n.get(i));
		}
	}

	@Override
	public void actionPerformed(ActionEvent arg0) {
		String c = arg0.getActionCommand();
		if(c.equals("Connect")) {
			try
			{
			Registry reg = LocateRegistry.getRegistry(address.getText().trim());
			Server serv = (Server) reg.lookup("Server");
			servers.add(serv);
			pn("Correctly connected to '" + address.getText().trim() + "' server");
			}
			catch(RemoteException e)
			{
				pn(e.getLocalizedMessage());
				e.printStackTrace();
			}
			catch(NotBoundException e)
			{
				pn(e.getLocalizedMessage());
				e.printStackTrace();
			}
		}
		else if(c.equals("Add")) {
			int num = Integer.parseInt(addNumN.getText().trim());
			int port = Integer.parseInt(addPort.getText().trim());
			
			ArrayList<Pair<NodeId, Integer>> nodes = new ArrayList<Pair<NodeId, Integer>>(num);
			for(int i = 0; i < num; ++i) {
				nodes.add(new Pair<NodeId,Integer>(new NodeId(NodeId.encodeId(String.valueOf(i+50)))/*NodeId.random()*/, port++));
			}
			
			try {
				servers.get(servers.size()-1).addNodes(nodes);
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else if(c.equals("New net")) {
//			try {
//				servers.get(servers.size()-1).createNewNet();
//			} catch (RemoteException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			} catch (SocketException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			} catch (NodeStateException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
			executor.execute(new ServerNewNet());
		}
		else if(c.equals("Send rand")) {
			try {
				servers.get(servers.size()-1).sendRandom(new Integer(1));
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		else if(c.equals("Collect")) {
			RegistratorData data;
			try {
				data = servers.get(servers.size()-1).getRegistredData();
			
				pn(("----------Server: " + servers.get(servers.size()-1) + "----------"));
				pn(("kSearchNum:     " + data.getKSearchNum() + ""));
				pn(("kSearchTimeout: " + data.getKSearchTimeout() + ""));
				pn(("nodeNotFound:   " + data.getNodeNotFound() + ""));
				pn(("sendNum:        " + data.getSendNum() + ""));
				pn(("sendNumTimeout: " + data.getSendNumTimeout() + "\n"));
			} catch (RemoteException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		try {
			ArrayList<NodeId> n = servers.get(servers.size()-1).getNodes();
			updateNodes(n);
		} catch (RemoteException e) {
			pn("Can't update nodes\n" + e.getLocalizedMessage());
			e.printStackTrace();
		}
	}
	
	private class ServerListener implements Runnable {

		private String s = null;
		private BufferedReader stdInput;
		public ServerListener(BufferedReader stdInput) {
			this.stdInput = stdInput;
		}
		@Override
		public void run() {
			try {
				while ((s = stdInput.readLine()) != null) {
					pn(s);

//					try {
//						Thread.sleep(30);
//					} catch (InterruptedException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
				    
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private class ServerNewNet implements Runnable {
		@Override
		public void run() {
			try {
				synchronized(servers) {
					servers.get(servers.size()-1).createNewNet();
				}
			} catch (IOException e) {
				pn(e.getLocalizedMessage());
				e.printStackTrace();
			} catch (NodeStateException e) {
				pn(e.toString());
				e.printStackTrace();
			}
		}
	}




	@Override
	public void windowActivated(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void windowClosed(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void windowClosing(WindowEvent arg0) {
		if(rmireg != null) {
			rmireg.destroy();
		}
		
		if(serverProcess != null) {
			serverProcess.destroy();
		}
		
	}

	@Override
	public void windowDeactivated(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void windowDeiconified(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void windowIconified(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void windowOpened(WindowEvent arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void itemStateChanged(ItemEvent arg0) {
		Object src = arg0.getSource();
		if(src == nodesCB && nodes.size() > 0) {
				Pair<String, String> p;
				try {
					int index = nodesCB.getSelectedIndex();
					if(index < 0) index = 0;
					if(index >= nodes.size()) index = nodes.size();
					p = servers.get(servers.size() - 1).getEnv(nodes.get(index));
					if (p == null) {
						leafSet.setText("No such node: '"
								+ nodes.get(nodesCB.getSelectedIndex()) + "'\n");
						routingTable.setText("No such node: '"
								+ nodes.get(nodesCB.getSelectedIndex()) + "'\n");
					}
					leafSet.setText(p.first);
					routingTable.setText(p.second);
				} catch (RemoteException e) {
					pn(e.getLocalizedMessage());
					e.printStackTrace();
				}
		}
		
	}
}
